//
// Ephi - simulation of magnetic fields and particles
// Copyright (C) 2007 Indrek Mandre <indrek(at)mare.ee>
// For more information please see http://www.mare.ee/indrek/ephi/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#ifndef __scene_hpp__
#define __scene_hpp__

#include <vector>

#include "math3d.hpp"
#include "utils.hpp"
#include "screen.hpp"
#include "threading.hpp"

class ElectroDynamics;
class Screen;

/**
  * Scene is used to calculate maps of data about the system and its fields that can be rendered onto a Screen.
  * The idea is that the screen defines a projection. You first use the Scene to calculate
  * a set of values that are stored withing the Scene object. Later this data can be visualized on the Screen
  * with different parameters.
  */
class Scene
{
#ifndef SWIG
  template <class T>
  struct value_map
  {
    value_map () : data(0) { }

    bool init (size_t w, size_t h)
    {
      if ( data != 0 && this->w == w && this->h == h )
          return false;
      delete [] data;
      this->w = w;
      this->h = h;
      data = new T [w * h];
      return true;
    }

    void clear ()
    {
      memset (data, 0, sizeof (T) * w * h);
    }

    bool valid (size_t x, size_t y) const
    {
      return x < w && y < h;
    }

    T get (size_t x, size_t y) const
    {
      if ( !valid(x, y) )
        {
          T tmp;
          tmp = 0;
          return tmp;
        }
      return data[y * w + x];
    }

    void set (size_t x, size_t y, T value) const
    {
      data[y * w + x] = value;
    }

    bool valid (const vect3d& v) const { return valid(v.x, v.y); }
    T get (const vect3d& v) const { return get(v.x, v.y); }

    bool valid (prec_t x, prec_t y) const
    {
      return valid ((size_t)floor(x), (size_t)floor(y));
    }

    T get (prec_t x, prec_t y) const
    {
      return get ((size_t)floor(x), (size_t)floor(y));
    }

    ~value_map ()
    {
      delete [] data;
    }

    operator T* () { return data; }
    operator const T* () const { return data; }

    size_t w, h;
    T *data;
  };
#endif

public:
  Scene (ElectroDynamics& dynamics);
  ~Scene ();

  struct Source
  {
  public:
    virtual ~Source();
    virtual prec_t getScalar (const vect3d& pos) = 0;
  };

  enum ecalc_t { CALC_BFIELD = 1, CALC_EFIELD, CALC_POTENTIAL, CALC_SCALAR };

  /** Calculate field values based on screen. */
  void calc (Screen& screen, ecalc_t ct = CALC_BFIELD, Source *src = 0);
  /** Render field onto screen. */
  void render_map (Screen &screen);
  /** Render line integral convolutions of the field onto the screen. */
  void render_lic (Screen &screen);
  /** Render line integral convolution animation of the field onto the screen. */
  void render_lic_animation (Screen &screen, const char *name_prefix, int frames);

  /** Define coloring scheme used at render operations.
    * @param levels Number of levels to use, with levels=0 meaning all levels
    * @param use_log Use logarithmic scale for values
    */
  void set_coloring (size_t levels = 0, bool use_log = false);

  const Screen::color& cmap (prec_t bf);

  /** Render a lines following the field from given position.
    * @param pos Start rendering from
    * @param color Render with given color
    * @param limit_dist Limit distance to draw the line, with -1 being infinite
    * @param limit_pos When line comes to proximity terminate rendering
    * @param limit_pos_radius Proximity to limit_pos that is used to terminate drawing
    * @param backwards Draw backwards to the field direction
    * @return True if the line drawn connected on ends.
    */
  bool render_bline (Screen &screen, const vect3d& pos, const Screen::color& color, prec_t limit_dist = -1,
      const vect3d& limit_pos = vect3d(0, 0, 0), prec_t limit_pos_radius = 0.0, bool backwards = false);

  /** Precalibrate field values for the render_bline_range() from the given position.
    */
  void calibrate_bline_step (const vect3d& pos, prec_t step);

  /** Render a series of lines in the given range. */
  void render_bline_range (Screen &screen, const vect3d& pos0, const vect3d& pos1,
      const Screen::color& color, prec_t first_factor = 1.0, prec_t dist_limit = -1,
      const vect3d& line_end = vect3d(0, 0, 0), prec_t line_end_radius = 0.0);

  /** Find the position of a line close to any structures in the simulation. */
  bool find_untouching_bline (vect3d& result, const vect3d& pos0, vect3d& pos1,
      prec_t limit_dist = -1, prec_t limit_pos_radius = 0.0);

  /** Get field minimum magnitude. */
  prec_t get_fmin() { return min_bf; }
  /** Get field maximum magnitude. */
  prec_t get_fmax() { return max_bf; }
  /** Get field correction add. */
  prec_t get_fadd() { return bfadd; }

  /** Recalibrate fields to given values used at rendering, values outside the given range are truncated. */
  void calibrate_field (prec_t min, prec_t max, prec_t add = 0)
  { min_bf = min; max_bf = max; bfadd = add; update_levels(); }

  void renderGnuplotData (Screen& screen, const char *path);

private:
  ElectroDynamics& dynamics;
  value_map<vect3d> vmap;
  value_map<vect3d> tmap; // translated and normalized vmap
  value_map<prec_t> smap; // scalar map
  value_map<prec_t> noise; // noise for lic
  prec_t min_bf;
  prec_t max_bf;
  std::vector<Screen::color> colors;
  bool use_log;
  size_t levels;
  prec_t max_level;
  prec_t factor;
  prec_t range;
  bool has_data;
  prec_t bfadd;

  struct calctask : Task
  {
    size_t part;
    prec_t min_f, max_f;
    Scene *scene;
    ecalc_t ct;
    Screen *screen;
    Source *src;

    void execute () { scene->run_calctask (this); }
  };
  friend struct calctask;

  struct lictask : Task
  {
    int part;
    Scene *scene;
    Screen *screen;
    value_map<prec_t> *noise;

    void execute () { scene->run_lictask (this); }
  };
  friend struct lictask;

  void run_calctask (calctask *task);
  void run_lictask (lictask *task);

  prec_t step, mbf;

  void lic_integrate (const value_map<prec_t>& noise, const vect3d& pos,
      prec_t& total_ds, prec_t& total_c, bool neg = false);
public:
  static prec_t hf (prec_t a, prec_t b, prec_t phi);
private:
  void lic_integrate_hf (const value_map<prec_t>& noise, const vect3d& pos, prec_t phi,
      prec_t& total_ds, prec_t& total_c, bool neg = false);
  void update_levels ();
};

#endif // !__scene_hpp__

